package com.aternity.vbd.collect;

import com.google.common.base.Preconditions;
import dnl.persistence.JdbcTemplateToolz;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.sql.SQLException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Daniel Orr
 */
@Component
public class DdlManager {

    private static final Logger logger = LoggerFactory.getLogger(DdlManager.class);

    private ResourceManager resourceManager;

    private JdbcTemplate jdbcTemplate;

    private Map<String, String> tableName2Ddl;

    @Autowired
    @Required
    public void setResourceManager(ResourceManager resourceManager) {
        this.resourceManager = resourceManager;
    }

    @Autowired
    @Required
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void createTablesIfNecessary() {
        resolveDdlData();
        processDdls();
    }

    public Collection<String> getTableNames() {
        if (tableName2Ddl == null) {
            if (!resolveDdlData()) {
                return null;
            }
        }
        return tableName2Ddl.keySet();
    }

    private void processDdls() {
        JdbcTemplateToolz jdbcTemplateToolz = new JdbcTemplateToolz(jdbcTemplate);
        final Set<String> tableNames = tableName2Ddl.keySet();
        for (String tableName : tableNames) {
            try {
                if (jdbcTemplateToolz.tableExists(tableName)) {
                    logger.info("Table '{}' exists", tableName);
                } else {
                    logger.info("Creating table '{}'", tableName);
                    jdbcTemplateToolz.executeDdl(tableName2Ddl.get(tableName));
                }
            } catch (SQLException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    public DbStatus getDbStatus() {
        JdbcTemplateToolz toolz = new JdbcTemplateToolz(jdbcTemplate);
        final Set<String> allTableNames;
        try {
            allTableNames = toolz.getExistingTableNames(false, true);
            final Collection<String> tableNames = getTableNames();
            if (tableNames == null) {
                return DbStatus.NO_DDL_DATA;
            } else if (allTableNames.containsAll(tableNames)) {
                return DbStatus.TABLES_EXIST;
            } else if (!Collections.disjoint(allTableNames, tableNames)) {
                return DbStatus.PARTIAL;
            } else {
                return DbStatus.TABLES_NOT_EXIST;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return DbStatus.UNKNOWN;
    }

    public void recreateAllTables() {
        removeAll();
        createAll();
    }

    public void createAll() {
        logger.info("createAll() :: start");
        final String createTablesScript = resourceManager.getCreateTablesScript();
        executeScript(createTablesScript, ScriptSplittingStrategy.semicolonSplitter);
        createTriggersAndProcedures();
        createJob();
        logger.info("createAll() :: end");
    }

    public void createTriggersAndProcedures() {
        logger.info("createTriggersAndProcedures()");
        final String createTriggersScript = resourceManager.getCreateTriggersAndProceduresScript();
        executeScript(createTriggersScript, ScriptSplittingStrategy.customSplitter);
    }

    public void createJob() {
        logger.info("createJob()");
        final String registerJobScript = resourceManager.getRegisterJobScript();
        executeScript(registerJobScript, ScriptSplittingStrategy.customSplitter);
    }

    public void removeAll() {
        logger.info("removeAll() :: start");
        final String dropAllScript = resourceManager.getDropAllScript();
        executeScript(dropAllScript, ScriptSplittingStrategy.semicolonSplitter);
        dropSchedulerJob();
        logger.info("removeAll() :: end");
    }

    private void dropSchedulerJob() {
        logger.info("dropSchedulerJob()");
        final String dropJobScript = resourceManager.getDropJobScript();
        executeScript(dropJobScript, ScriptSplittingStrategy.customSplitter);
    }

    private void executeScript(String script, ScriptSplittingStrategy splittingStrategy) {
        Preconditions.checkNotNull(script);
        final String[] splits = splittingStrategy.getCommands(script);
        for (String ddl : splits) {
            String ddlToExecute = ddl.trim();
            if(ddlToExecute.endsWith("/")){
                ddlToExecute = ddlToExecute.substring(0, ddlToExecute.length()-2);
            }
            if(!ddlToExecute.isEmpty()){
                try {
                    jdbcTemplate.execute(ddlToExecute);
                } catch (RuntimeException e) {
                    logger.error("Error executing script '{}': {}", ddlToExecute, e.getMessage());
                }
            }
        }
    }

    /**
     * @return true if all OK
     */
    public boolean resolveDdlData() {
        final String dataDdl = resourceManager.getCreateTablesScript();
        if (dataDdl == null) {
            logger.error("No DDL data.");
            return false;
        }
        final String[] splits = StringUtils.split(dataDdl, ";");
        tableName2Ddl = new HashMap<>();
        for (String ddl : splits) {
            String trimmed = ddl.trim();
            if (trimmed.isEmpty()) {
                continue;
            }
            final String tableName = resolveTableName(ddl);
            if (tableName != null) {
                tableName2Ddl.put(tableName.toLowerCase(), ddl);
            }
        }
        return true;
    }

    private String resolveTableName(String ddl) {
        Pattern pattern = Pattern.compile(".*?create table\\s+(\\w+)\\s+\\(.*?", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(ddl);

        try {
            if (matcher.find()) {
                final String tableName = matcher.group(1);
                logger.info("Resolving DDL for table: {}", tableName);
                return tableName;
            }
        } catch (Exception ex) {
            logger.error("No table definitions found in DDL: {}", ddl);
            throw ex;
        }
        return null;
    }

}
